/* TransactForm.cpp
 Michael Zahniser, 2008
 
 Function definitions for the TransactForm class.
 */

#include "TransactForm.h"

#include "_ncurses/Form.h"
#include "Date.h"
#include "Command.h"

#include <sstream>

using std::ostringstream;
using std::istringstream;



TransactForm::TransactForm(Database &database)
	: database(database)
{
    ostringstream out;
    out << Date::Today();
	today = out.str();
    
	form.GetField("Date")
		.SetDefault(today)
		.OnComplete(&CompleteDate, &today)
		.OnValidate(&ValidateDate);
	form.GetField("Amount")
		.OnValidate(&ValidateMoney);
	form.GetField("To")
		.OnValidate(&ValidateAccount, this)
		.OnComplete(&CompleteAccount, this)
		.OnEnter(&EnterToAccount, this);
	form.GetField("From")
		.OnValidate(&ValidateAccount, this)
		.OnComplete(&CompleteAccount, this);
	form.GetField("Comment");
}



const string &TransactForm::Run()
{
    static Transaction transaction;
    
    if(!form.Run())
    {
		static const string cancel = "Transaction input canceled.";
		return cancel;
    }
	
    transaction.SetDate(form.GetField("Date").Value());
	
    istringstream in(form.GetField("Amount").Value());
    double amount;
    in>>amount;
    transaction.SetAmount(amount);
	
    transaction.SetToAccount(form.GetField("To").Value());
    transaction.SetFromAccount(form.GetField("From").Value());
    transaction.SetComment(form.GetField("Comment").Value());
	
    return database.AddTransaction(transaction);
}



// Get all accounts matching the given name prefix.
const vector<string> &TransactForm::MatchAccountName(const string &prefix)
{
    const map<string, Account> &accounts = database.Accounts();
	
    static vector<string> matches;
    matches.clear();
	
    map<string, Account>::const_iterator it = accounts.begin();
    for( ; it != accounts.end(); ++it)
		if(Command::HasPrefix(it->first, prefix))
			matches.push_back(it->first);
	
    return matches;
}



// Get the prefix shared by all entries in the given vector,
// starting with the given character position.
string TransactForm::CommonPrefix(const vector<string> &names, size_t pos)
{
    if(names.size() < 2)
		return "";
    
    while(pos != names.front().length())
    {
		vector<string>::const_iterator it = names.begin();
		char first = (*it)[pos];
		++it;
		for(; it != names.end(); ++it)
			if(pos == it->length() || (*it)[pos] != first)
				return it->substr(0, pos);
		++pos;
    }
	
    return names.front();
}



// Guess the account the transfer will be from, based on the history
// of transfers to the given account.
string TransactForm::GuessFromAccount(const string &toAccount)
{
    vector<Transaction> transactions = database.Transactions();
	
    vector<Transaction>::const_iterator it = transactions.end();
    while(it != transactions.begin())
    {
		--it;
		if(it->GetToAccount() == toAccount)
			return it->GetFromAccount();
    }
	
    return "";
}



bool TransactForm::ValidateDate(string &input, void *)
{
    if(input.length() != 8)
		return false;
    
    string::const_iterator it = input.begin();
    for( ; it != input.end(); ++it)
		if((*it < '0') | (*it > '9'))
			return false;
    
    return true;
}



bool TransactForm::CompleteDate(string &input, void *data)
{
	if(!data || input.length() >= 8)
		return false;
    const string &today = *reinterpret_cast<string *>(data);
	
	// Only tab-complete if what exists so far matches today's date.
	if(today.substr(0, input.length()) != input)
		return false;
	// Complete first the year, then the month, then the day.
	input = today.substr(0, 4 + 2 * ((input.length() >= 4) + (input.length() >= 6)));
	return true;
}



bool TransactForm::ValidateMoney(string &input, void *)
{
    if(input.empty())
		return false;
    
    int decimals = -1;
    string::const_iterator it = input.begin();
    if(*it == '-')
    {
        ++it;
        if(it == input.end())
            return false;
    }
	
    for( ; it != input.end(); ++it)
    {
		if(decimals != -1)
		{
			++decimals;
			if((decimals > 2) | (*it == '.'))
				return false;
		}
		if(*it == '.')
			decimals = 0;
		else if((*it < '0') | (*it > '9'))
			return false;
    }
    
    return true;
}



bool TransactForm::ValidateAccount(string &input, void *data)
{
    if(!data)
		return false;
    TransactForm *form = reinterpret_cast<TransactForm *>(data);
    
    const vector<string> &matches = form->MatchAccountName(input);
    return (matches.size() == 1 && matches.front() == input);
}



bool TransactForm::CompleteAccount(string &input, void *data)
{
    if(!data)
		return false;
    TransactForm *form = reinterpret_cast<TransactForm *>(data);
    
    const vector<string> &matches = form->MatchAccountName(input);
    if(matches.empty())
		return false;
    else if(matches.size() == 1)
		input = matches.front();
    else
		input = form->CommonPrefix(matches, input.size());
	
    return true;
}



bool TransactForm::EnterToAccount(string &input, void *data)
{
    if(!data)
		return false;
    TransactForm *form = reinterpret_cast<TransactForm *>(data);
	
    string guess = form->GuessFromAccount(input);
    if(!guess.empty())
		form->form.GetField("From").SetDefault(guess);
	
    return true;
}
